#pragma once

#include "Constants.h"
#include "Mathematics.h"

class Frustum {
public:
	Frustum (u32 w = BASE_WIDTH, u32 h = BASE_HEIGHT, fp32 near_v = BASE_NEAR_PLANE, fp32 far_v = BASE_FAR_PLANE, fp32 fov = BASE_FOV) : near_v(near_v), far_v(far_v) {
		xfov = fov;
		yfov = ((fp32)h * (fov) / (fp32)w);
		tg_half_hor_fov = tan(TO_RADIAN(fov) / 2.f);
		tg_half_vert_fov = tan(((fp32)h * TO_RADIAN(fov) / (fp32)w) / 2);
	}

	inline void update () {
		PerspectiveParams pp;
		int w, h;
		SDL_GetWindowSize(g_window, &w, &h);
		tg_half_hor_fov = tan(TO_RADIAN(pp.fov) / 2.f);
		tg_half_vert_fov = tan(((fp32)h * TO_RADIAN(pp.fov) / (fp32)w) / 2);
	}

	// Is point in frustum
	inline bool frustumTest (const Vector3f point) {
		// should use direction of camera instead of position
		const Vector3f &cam_pos	= CameraManager::instance().pos();
		const Vector3f &cam_dir	= CameraManager::instance().dir();

		Vector3f cam2point_vec	(point);
		cam2point_vec.sub		(cam_pos);

		const fp32 z_dist		= Vector3f::dot(cam2point_vec, cam_dir);
		if (z_dist < 0.f)		return false;

		Vector3f z_point		(cam_pos);
		z_point.add				(Vector3f::mul(cam_pos, z_dist));

		Vector3f lr_proj_vector;
		lr_proj_vector.set		(point.x(), cam_pos.y(), point.z());
		lr_proj_vector.nrm		();
		const fp32 lr_angle		= acos(Vector3f::dot(cam_dir, lr_proj_vector));
		const bool left_right_plane = lr_angle < TO_RADIAN(xfov);

		Vector3f ud_proj_vector;
		ud_proj_vector.set		(cam_pos.x(), point.y(), point.z());
		ud_proj_vector.nrm		();
		const fp32 ud_angle		= acos(Vector3f::dot(cam_dir, ud_proj_vector));
		const bool up_down_plane = ud_angle < TO_RADIAN(yfov);

		const bool near_far_plane = (z_dist <= far_v) && (z_dist >= near_v);

		return near_far_plane && up_down_plane && left_right_plane;
	}

private:
	fp32 near_v, far_v;
	fp32 tg_half_hor_fov;
	fp32 tg_half_vert_fov;
	fp32 xfov, yfov;
};